Java 26일 코스 - Day 20: 파일 입출력

Day 20: 파일 입출력

Java NIO(New I/O)의 FilesPath 클래스는 파일 입출력을 간편하고 안전하게 처리합니다. 기존 java.io 패키지보다 현대적이며, 코드도 훨씬 간결합니다.

Path와 Files 기본

파일 경로를 다루고 기본적인 파일 작업을 수행합니다.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathBasic {
    public static void main(String[] args) throws IOException {
        // Path 생성
        Path currentDir = Path.of(".");
        Path filePath = Path.of("test.txt");
        Path absolutePath = filePath.toAbsolutePath();

        System.out.println("현재 디렉토리: " + currentDir.toAbsolutePath());
        System.out.println("파일 경로: " + absolutePath);
        System.out.println("파일 이름: " + filePath.getFileName());
        System.out.println("부모 경로: " + absolutePath.getParent());

        // 파일 존재 여부
        System.out.println("존재? " + Files.exists(filePath));

        // 파일 생성 및 쓰기
        Path newFile = Path.of("hello.txt");
        Files.writeString(newFile, "안녕하세요, Java 파일 입출력!\n오늘도 좋은 하루!");
        System.out.println("파일 생성됨: " + Files.exists(newFile));

        // 파일 정보
        System.out.println("크기: " + Files.size(newFile) + " bytes");
        System.out.println("읽기 가능? " + Files.isReadable(newFile));
        System.out.println("쓰기 가능? " + Files.isWritable(newFile));

        // 정리
        Files.deleteIfExists(newFile);
    }
}

파일 읽기

다양한 방식으로 파일 내용을 읽는 방법입니다.

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;

public class FileReadExample {
    public static void main(String[] args) throws IOException {
        // 테스트 파일 생성
        Path path = Path.of("sample.txt");
        String content = """
                첫 번째 줄입니다.
                두 번째 줄입니다.
                세 번째 줄입니다.
                Java 파일 입출력을 배워봅시다.
                마지막 줄입니다.
                """;
        Files.writeString(path, content, StandardCharsets.UTF_8);

        // 방법 1: 전체 문자열로 읽기
        String allContent = Files.readString(path);
        System.out.println("=== 전체 읽기 ===");
        System.out.println(allContent);

        // 방법 2: 줄 단위 리스트로 읽기
        List<String> lines = Files.readAllLines(path);
        System.out.println("=== 줄 단위 ===");
        for (int i = 0; i < lines.size(); i++) {
            System.out.println((i + 1) + ": " + lines.get(i));
        }

        // 방법 3: Stream으로 읽기 (대용량 파일에 적합)
        System.out.println("=== Stream 읽기 ===");
        try (Stream<String> lineStream = Files.lines(path)) {
            lineStream
                .filter(line -> !line.isBlank())
                .map(String::trim)
                .forEach(System.out::println);
        }

        // 방법 4: 바이트 단위 읽기
        byte[] bytes = Files.readAllBytes(path);
        System.out.println("바이트 크기: " + bytes.length);

        // 정리
        Files.deleteIfExists(path);
    }
}

파일 쓰기

파일에 데이터를 쓰는 다양한 방법입니다.

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;

public class FileWriteExample {
    public static void main(String[] args) throws IOException {
        // 방법 1: 문자열 한 번에 쓰기
        Path path1 = Path.of("output1.txt");
        Files.writeString(path1, "한 줄로 쓰기\n두 번째 줄");
        System.out.println("output1.txt 생성됨");

        // 방법 2: 줄 단위 리스트 쓰기
        Path path2 = Path.of("output2.txt");
        List<String> lines = List.of(
            "이름: 홍길동",
            "나이: 25",
            "직업: 개발자",
            "언어: Java"
        );
        Files.write(path2, lines, StandardCharsets.UTF_8);
        System.out.println("output2.txt 생성됨");

        // 방법 3: 기존 파일에 추가 (APPEND)
        Files.writeString(path1, "\n추가된 내용!",
            StandardOpenOption.APPEND);
        System.out.println("output1.txt에 내용 추가됨");

        // 방법 4: BufferedWriter 사용 (대용량)
        Path path3 = Path.of("output3.txt");
        try (BufferedWriter writer = Files.newBufferedWriter(path3)) {
            for (int i = 1; i <= 100; i++) {
                writer.write("줄 번호 " + i);
                writer.newLine();
            }
        }
        System.out.println("output3.txt 생성됨 (" + Files.size(path3) + " bytes)");

        // 출력 확인 후 정리
        System.out.println("\n=== output1.txt 내용 ===");
        System.out.println(Files.readString(path1));

        Files.deleteIfExists(path1);
        Files.deleteIfExists(path2);
        Files.deleteIfExists(path3);
    }
}

디렉토리 작업과 파일 탐색

디렉토리 생성, 파일 복사/이동, 디렉토리 내용 탐색을 다룹니다.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.stream.Stream;

public class DirectoryExample {
    public static void main(String[] args) throws IOException {
        // 디렉토리 생성
        Path dir = Path.of("test-dir");
        Path subDir = Path.of("test-dir", "sub", "deep");
        Files.createDirectories(subDir); // 중간 경로도 자동 생성
        System.out.println("디렉토리 생성: " + subDir);

        // 파일 생성
        Path file1 = dir.resolve("file1.txt");
        Path file2 = dir.resolve("file2.java");
        Path file3 = subDir.resolve("file3.txt");
        Files.writeString(file1, "파일 1 내용");
        Files.writeString(file2, "public class Test {}");
        Files.writeString(file3, "중첩된 파일");

        // 파일 복사
        Path copied = dir.resolve("file1_copy.txt");
        Files.copy(file1, copied, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("복사됨: " + copied);

        // 디렉토리 내용 나열 (1단계만)
        System.out.println("\n=== 디렉토리 내용 ===");
        try (Stream<Path> entries = Files.list(dir)) {
            entries.forEach(path -> {
                String type = Files.isDirectory(path) ? "[DIR]" : "[FILE]";
                System.out.println(type + " " + path.getFileName());
            });
        }

        // 재귀적 파일 탐색 (모든 하위 포함)
        System.out.println("\n=== 전체 트리 ===");
        try (Stream<Path> walk = Files.walk(dir)) {
            walk.forEach(path -> {
                int depth = dir.relativize(path).getNameCount();
                String indent = "  ".repeat(depth);
                System.out.println(indent + path.getFileName());
            });
        }

        // 특정 확장자 파일 찾기
        System.out.println("\n=== .txt 파일만 ===");
        try (Stream<Path> found = Files.find(dir, 10,
                (path, attrs) -> path.toString().endsWith(".txt") && attrs.isRegularFile())) {
            found.forEach(System.out::println);
        }

        // 정리: 역순으로 삭제 (파일 먼저, 디렉토리 나중에)
        try (Stream<Path> walk = Files.walk(dir)) {
            walk.sorted((a, b) -> b.compareTo(a))
                .forEach(path -> {
                    try { Files.deleteIfExists(path); }
                    catch (IOException ignored) {}
                });
        }
        System.out.println("\n정리 완료");
    }
}

오늘의 연습문제

  1. CSV 파서: CSV 파일을 읽어 각 행을 파싱하고, 특정 열의 합계/평균을 계산하는 프로그램을 작성하세요. (CSV 파일을 먼저 프로그래밍으로 생성하세요)

  2. 로그 분석기: 로그 파일을 Stream으로 읽어서 ERROR 레벨의 로그만 필터링하고, 시간대별로 에러 수를 집계하는 프로그램을 작성하세요.

  3. 파일 백업 유틸리티: 지정한 디렉토리의 모든 .txt 파일을 backup/ 디렉토리에 날짜 접미사를 붙여 복사하는 프로그램을 작성하세요. (예: file.txt -> backup/file_20260422.txt)

이 글이 도움이 되었나요?